/*
*    Qizx/open 3.0 beta
*
* This code is the open-source version of Qizx.
* Copyright (C) 2004-2009 Axyana Software -- All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License 
*  Version 1.1 (the "License"); you may not use this file except in 
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
*  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
*  for the specific language governing rights and limitations under the
*  License.
*
* The Initial Developer of the Original Code is Xavier Franc - Axyana Software.
*
*/
using System;
using DataModelException = System.Qizx.Api.DataModelException;
using Node = System.Qizx.Api.Node;
using QName = System.Qizx.Api.QName;
using NamespaceContext = System.Qizx.Util.NamespaceContext;
using CoreNode = System.Qizx.Xdm.XdmNode;
using NSNode = System.Qizx.Xdm.XdmNSAttribute;
namespace System.Qizx.Xdm {

    /// <summary> An implementation of XMLPushStream that builds a CoreDataModel tree.
    /// <p>
    /// Used in XQ node constructors.
    /// </summary>
    public class CorePushBuilder : XMLPushStreamBase {
        protected internal CoreDataModel dm;
        protected internal XdmNode root;

        protected internal XdmElement current;
        protected internal CoreNode previous;
        protected internal int orderStamp = 1;


        public CorePushBuilder(string baseURI)
            : this(baseURI, null) {
        }

        /// <summary> Extends XMLPushStreamBase only for traverse. Doesnt use its prefix map.</summary>
        public CorePushBuilder(string baseURI, NamespaceContext staticNS) {
            dm = new CoreDataModel(baseURI);
            if (dm._ownerDocument == null) {
                dm._ownerDocument = dm.newDocumentNode();
            }
            contextualMappings = staticNS;
        }

        public virtual BasicNode harvest() {
            return root;
        }

        public override string resolvePrefix(string prefix) {
            return (current == null) ? null : current.GetNamespaceOfPrefix(prefix);
        }

        public override void reset() {
            base.reset();
            previous = root = current = null;
        }

        public override void flush() {
        }

        public override bool putDocumentStart() {
            spaceNeeded = false;
            if (root != null)
                return false; // no more an error:
            // / throw new DataModelException("document inside document");
            root = current = dm.newDocumentNode();
            previous = null; // should be already
            return true;
        }

        public override void putDocumentEnd() {
            spaceNeeded = false;
            closeNode();
            spaceNeeded = false;
        }

        public override void flushElementStart(bool empty) {
            spaceNeeded = false;
            // Need to add prefixes for unmapped NS
            completeNameMappings();
            elementStarted = false;
        }

        public override void putElementStart(QName name) {
            if (maxVolume > 0 && volume > maxVolume)
                throw new DataModelException(VOLUME_LIMIT);
            if (elementStarted)
                flushElementStart(false);
            spaceNeeded = false;
            elementName = IQName.GetQName(name);
            elementStarted = true;
            attrCnt = 0;
            XdmElement e = dm.newElement((IQName)name);
            openNode(e);
            // marks the namespace stack
            nsContext.newLevel();
        }

        public override void putElementEnd(QName name) {
            if (maxVolume > 0 && volume > maxVolume)
                throw new DataModelException(VOLUME_LIMIT);
            if (elementStarted)
                flushElementStart(false);
            nsContext.popLevel();
            closeNode();
        }

        public override bool putNamespace(string prefix, string uri) {
            if (current == null)
                throw new DataModelException("stray namespace");
            if (uri == null)
                uri = ""; // null not liked by addNSMapping
            if (!addNSMapping(prefix, uri))
                throw new DataModelException("XQST0071", "duplicate namespace declaration '" + prefix + "'");
            return true;
        }

        protected internal virtual bool addNSMapping(string prefix, string ns) {
            nsContext.addMapping(prefix, ns);
            XdmNSAttribute nsNode = dm.newNSNode(prefix);
            nsNode._strValue = ns;
            if (!current.AddNamespace(nsNode))
                return false;
            volume += prefix.Length + ns.Length;
            return true;
        }

        public override void putAttribute(QName name, string value_Renamed, string type) {
            if (maxVolume > 0 && volume > maxVolume)
                throw new DataModelException(VOLUME_LIMIT);
            if (current == null)
                throw new DataModelException("stray attribute");
            if (current.FirstChild != null)
                throw new DataModelException("XQTY0024", "attribute added after contents");
            XdmAttribute attr = dm.newAttribute((IQName)name);
            attr._strValue = value_Renamed;
            attr.order = orderStamp++;
            if (!current.AddAttribute(attr)) {
                throw new DataModelException("XQDY0025", "duplicate attribute " + name);
            }
            // used for checking NS:
            rawAddAttribute(name, value_Renamed, type);
            volume += 10 + value_Renamed.Length;
        }

        public override void putText(string value) {
            if (value == null || value.Length == 0)
                return;
            if (maxVolume > 0 && volume > maxVolume)
                throw new DataModelException(VOLUME_LIMIT);
            if (elementStarted)
                flushElementStart(false);
            if (previous != null && previous.NodeType == System.Qizx.Api.XdmNodeType.Text) {
                previous.AddText(value);
            } else {
                addNode(dm.newTextNode(value));
            }
            spaceNeeded = false;
            volume += value.Length;
        }

        public override void putProcessingInstruction(string target, string value_Renamed) {
            checkPIValue(value_Renamed);
            if (maxVolume > 0 && volume > maxVolume)
                throw new DataModelException(VOLUME_LIMIT);
            if (elementStarted)
                flushElementStart(false);
            XdmProcessingInstruction pi = dm.newPINode(target, value_Renamed);
            addNode(pi);
            volume += value_Renamed.Length;
            spaceNeeded = false;
        }

        public override void putComment(string value_Renamed) {
            checkCommentValue(value_Renamed);
            if (maxVolume > 0 && volume > maxVolume)
                throw new DataModelException(VOLUME_LIMIT);
            if (elementStarted)
                flushElementStart(false);
            XdmComment c = dm.newCommentNode(value_Renamed);
            addNode(c);
            volume += value_Renamed.Length;
            spaceNeeded = false;
        }

        // adds a node as child of the current element.
        private void addNode(XdmNode n) {
            n._parent = current;
            if (previous != null) {
                previous.SetNextSibling(n);
                if (current == null) {
                    // orphan siblings: add a document node as parent
                    root = current = dm.newDocumentNode();
                    current.AppendChild(previous);
                    previous._parent = n._parent = current;
                }
            } else {
                if (current == null)
                    if (root != null)
                        throw new DataModelException("adding node after root");
                    else
                        root = current = (XdmElement)n;
                else
                    current.AppendChild(n);
                //source current._firstChild = n;
            }
            previous = n;
            // improves order comparisons tremendously :
            n.order = orderStamp++;
        }

        private void openNode(XdmElement e) {
            addNode(e);
            current = e;
            previous = null;
            volume += 10; // arbitrary
        }

        private void closeNode() {
            if (current == null)
                if (maxVolume > 0 && volume > maxVolume)
                    return;
                else
                    throw new DataModelException("no open element");
            previous = current;
            current = current._parent;
            volume += 10; // arbitrary
            spaceNeeded = false;
        }
    }
}